/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
/**
 * Software License, Version 1.0
 *
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 *
 *
 *Redistribution and use in source and binary forms, with or without
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For
 * further information contact the Community Grids Lab at
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and
 * wherever such third-party acknowledgments normally appear.
 *
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering,
 * shall not be used to endorse or promote products derived from this software
 * without prior written permission from Indiana University.  For written
 * permission, please contact the Advanced Research and Technology Institute
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering,
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 *
 *
 * Indiana University provides no reassurances that the source code provided
 * does not infringe the patent or any other intellectual property rights of
 * any other entity.  Indiana University disclaims any liability to any
 * recipient for claims brought by any other entity based on infringement of
 * intellectual property rights or otherwise.
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS.
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS",
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION
 *GENERATED USING SOFTWARE.
 */
package cgl.narada.sync;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.SimpleTimeZone;
import java.util.TimeZone;

import cgl.narada.service.timer.ClockI;
import cgl.narada.service.timer.HRClock;

/**
 *                         1                   2                   3
 *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *  |                           Seconds                             |
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *  |                  Seconds Fraction (0-padded)                  |
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *
 *  This class encapsulates the notion of a timestamp as in rfc2030.
 *  Logically it is the number of seconds since the beginning of the
 *  centure (in UTC time).
 *  It is represented as an 8 byte array, the first four bytes
 *  representing seconds and the next 4 bytes representing second
 *  fractions.
 * @author Hasan Bulut - hbulut@indiana.edu
 */
public class SyncTimestamp {
    //~ Static fields/initializers -----------------------------------------------

    /*
     *offSet = slope*Tempo + initOffSet (y = m*x + a)
     */
    private static double slope = 0; //offset line inclination in funtion of time
    private static long refTime = 0;
    /**
     * Holds the miliseconds count to be added to local clock for it to be set equal to the server
     */
    private static double initOffset; //Line initial value || ||
    private static TimeZone UTC = new SimpleTimeZone(0, "UTC");
    private static Calendar calendar =
            new GregorianCalendar(1900,
            Calendar.JANUARY, 1,
            0, 0, 0);
    private static Date startOfCentury;
    public static ClockI clock;

    static {
        calendar.setTimeZone(UTC);
        startOfCentury = calendar.getTime();
        clock = HRClock.getClock();
    }
    //~ Instance fields ----------------------------------------------------------
    private byte[] timestampData = {0, 0, 0, 0, 0, 0, 0, 0};
    private long fractionalPart = 0;
    private long integerPart = 0;
    private long timeInMicroSeconds = 0;

    //~ Constructors -------------------------------------------------------------
    /**
     * This constructs a timestamp initialized with the current updated date.
     * Date is updated using the offset value between the local machine and
     * atomic time servers.
     */
    public SyncTimestamp() {
        timeInMicroSeconds = getLogicTime(); //getSystemTime();//Troquei com a nova impl: método da variação linear do offset
        long usSinceStartOfCentury = timeInMicroSeconds - 1000 * startOfCentury.getTime();//Em microsegundos
        integerPart = usSinceStartOfCentury / 1000000;
        fractionalPart = usSinceStartOfCentury % 1000000;/// 1000;

        long temp1 = integerPart;
        for (int i = 3; i >= 0; i--) {
            timestampData[i] = (byte) (temp1);
            temp1 = temp1 >> 8;
        }
        temp1 = fractionalPart;
        for (int i = 7; i >= 4; i--) {
            timestampData[i] = (byte) (temp1);
            temp1 = temp1 >> 8;
        }
        this.setTimestamp(0, timestampData);
    }

    /**
     * This constructs a timestamp initialized with the data passed.
     *
     * @param offset int - data is read starting from offset value
     * @param data byte [] - byte array that contains timestamp information
     */
    public SyncTimestamp(int offset, byte[] data) {
        this.setTimestamp(offset, data);
    }

    //~ Methods ------------------------------------------------------------------
    /**
     * Pega o tempo do sistema
     * @author renato
     *
     */
    private static long getSystemTime() {
        return clock.getTimeMicroseconds();
    }

    /**
     * The offset varies with time, it is given in miliseconds
     *   y    =      m*x         +     b
     * offset = slope*SystemTime + initOffset
     * @return
     */
    public static long getOffset() {
        return (long) (slope * (getSystemTime() - refTime) + initOffset);
    }

    /**
     * Returns the time according to the synchronism pre established
     * @return
     */
    public static long getLogicTime() {
        return (getSystemTime() + getOffset());
    }

    public static long getRefTime() {
        return refTime;
    }

    public static void setRefTime(long refT) {
        SyncTimestamp.refTime = refT;
    }

    public static double getInitOffset() {
        return initOffset;
    }

    public static void setInitOffset(double initO) {
        initOffset = initO;
    }

    public static void addInitOffset(long initPlus) {
        initOffset += initPlus;
    }

    public static double getSlope() {
        return slope;
    }

    public static void setSlope(double slp) {
        slope = slp;
    }

    /**
     * Gets the millisecond value of this SyncTimestamp
     *
     * @return long - time value in milliseconds
     */
    public long getTimeInMicroSeconds() {
        return timeInMicroSeconds;
    }

    /**
     * Sets the SyncTimestamp with the given Date object
     *
     * @param timeInMicroSeconds - SyncTimestamp is initialized with this timeInMicroSeconds
     */
    public void setTimestamp(long timeInMicroSeconds) {

        this.timeInMicroSeconds = timeInMicroSeconds;//+offSet;
        long msSinceStartOfCentury = this.timeInMicroSeconds - 1000 * startOfCentury.getTime();
        integerPart = msSinceStartOfCentury / 1000000;
        fractionalPart = (msSinceStartOfCentury % 1000000);///1000;
        long temp = integerPart;
        for (int i = 3; i >= 0; i--) {
            timestampData[i] = (byte) (temp & 0xFF);
            temp = temp >> 8;
        }
        temp = fractionalPart;
        for (int i = 7; i >= 4; i--) {
            timestampData[i] = (byte) (temp & 0xFF);
            temp = temp >> 8;
        }
    }

    /**
     * Sets the timestamp with the given byte array and the start offset
     * within the array. Byte array should have size of offset+8 at least.
     *
     * @param offset int - start offset within the given byte array
     * @param data byte [] - contains SyncTimestamp information.
     */
    public void setTimestamp(int offset, byte[] data) {
        integerPart = 0;
        for (int i = 0; i <= 3; i++) {
            integerPart = (integerPart << 8) | (0xFF & data[offset + i]);
            timestampData[i] = data[offset + i];
        }
        fractionalPart = 0;
        for (int i = 4; i <= 7; i++) {
            fractionalPart = (fractionalPart << 8) | (0xFF & data[offset + i]);
            timestampData[i] = data[offset + i];
        }
        this.setTimeInMicroSeconds();
    }

    /**
     * Initializes the data of the SyncTimestamp with the given byte array
     *
     * @param timestampData byte [] - data of SyncTimestamp is initialized with
     * this byte array
     */
    public void setTimestampData(byte[] timestampData) {
        this.timestampData = timestampData;
    }

    /**
     * Returns timestamp data which keeps the time information in byte array
     * format.
     *
     * @return byte [] - data of SyncTimestamp
     */
    public byte[] getTimestampData() {
        return timestampData;
    }

    /**
     * Converts the date object to String format.
     *
     * @return String - String format representing date with fractional part
     */
    public String toString() {
        return "" + this.timeInMicroSeconds + " + " + fractionalPart + "ms";
    }

    /**
     * Computes the time of the date in milliseconds using integer and fraction
     * part of the timestamp.
     *
     * @return long - SyncTimestamp time in milliseconds
     */
    private long setTimeInMicroSeconds() {
        this.timeInMicroSeconds = (integerPart * 1000000
                + fractionalPart)
                + startOfCentury.getTime();
        return this.timeInMicroSeconds;
    }

    /**
     * Sets the date of the timestamp to the given milliseconds.
     *
     * @param timeInMilliSeconds - SyncTimestamp is initialized with this time
     * value
     *
     * @return long - SyncTimestamp time in milliseconds
     */
    public long setTimeInMicroSeconds(long timeInMicroSeconds) {
        this.setTimestamp(timeInMicroSeconds);
        this.timeInMicroSeconds = timeInMicroSeconds;
        return this.timeInMicroSeconds;
    }

    /**
     * If int value of byte is negative, it is returned after added with 256,
     * otherwise the int value of the byte is returned.
     *
     * @param b byte
     *
     * @return  int
     */
    private int mp(byte b) {
        int bb = b;
        return (bb < 0) ? 256 + bb : bb;
    }
    /*public static void main(String a[]){
    SyncTimestamp ntpts = new SyncTimestamp();

    ntpts.setTimeInMilliSeconds(da.getTime());
    System.out.println(ntpts);
    System.out.println(da+" - "+da.getTime());
    //System.out.println("Anos: "+ntpts.ge0)
    }*/
}
